﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace EasyConveyor
{
	internal sealed class Pipeline
	{
		public event EventHandler<MiddlewareErrorEventArgs> Error;
		public event EventHandler<RequestReceivedEventArgs> RequestReceived;
		public event EventHandler<RequestCompletedEventArgs> RequestCompleted;
		public event EventHandler<MiddlewareInitializeEventArgs> MiddlewareInitialize;

		private readonly Thread _worker;
		private bool _running;
		private Action<string> _logger;

		public Pipeline(ActivatorBase activator, List<Type> middleWareTypes, Action<string> traceLog)
		{
			ID = Guid.NewGuid();

			Activator = activator;
			MiddleWareTypes = middleWareTypes;

			Logger = traceLog;

			Log("Pipeline created:{0}\tID = {1}{0}\tActivator = {2}", Environment.NewLine, ID, Activator.GetType().FullName);

			_worker = new Thread(Work);
			_worker.Start();
			Log("Worker Started:{0}\tPipeline ID = {1}", Environment.NewLine, ID);
		}

		~Pipeline()
		{
			StopWorker();
		}

		public Guid ID { get; private set; }

		public Pipeline(ActivatorBase activator, List<Type> middleWareTypes) : this(activator, middleWareTypes, null)
		{
		}

		public ActivatorBase Activator { get; private set; }
		public List<Type> MiddleWareTypes { get; private set; }
		
		private void StopWorker()
		{
			Log("Stopping Worker:{0}\tPipeline ID = {1}", Environment.NewLine, ID);
			_running = false;
			if (!_worker.Join(10000))
				_worker.Abort();

			Log("Worker Stopped:{0}\tPipeline ID = {1}", Environment.NewLine, ID);
		}

		private void Work()
		{
			try
			{
				_running = true;
				while (_running)
				{
					var context = Activator.GetNext();
					if (context == null)
						continue;

					OnRequestReceived(context);

					var executeNextStep = true;
					var errorOccurred = false;
					foreach (var type in MiddleWareTypes)
					{
						Log("Preparing to execute middleware:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tMiddleware Type = {3}",
							Environment.NewLine, ID, context, type.FullName);
						if ((executeNextStep = ExecuteMiddleware(type, context, out errorOccurred)))
						{
							continue;
						}
						Log(
							"Middleware denied processing of downstream middleware instances:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tMiddleware Type = {3}",
							Environment.NewLine, ID, context, type.FullName);
					}

					if (!errorOccurred)
						OnRequestCompleted(context, executeNextStep);
				}
			}
			catch (ThreadAbortException)
			{
				Thread.ResetAbort();
			}
		}

		private bool ExecuteMiddleware(Type type, object context, out bool errorOccured)
		{
			IMiddleware middleware = null;
			try
			{
				Log("Initializing new instance of middleware:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tMiddleware Type = {3}", Environment.NewLine, ID, context, type.FullName);
					
				middleware = (IMiddleware)System.Activator.CreateInstance(type);

				OnMiddlewareInitialize(middleware);

				Log("Running middleware:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tMiddleware Type = {3}", Environment.NewLine, ID, context, type.FullName);
				
				var toReturn = middleware.Run(context);
				errorOccured = false;
				return toReturn;
			}
			catch (Exception ex)
			{
				OnError(type, middleware, context, ex);
				errorOccured = true;
				return false;
			}
		}

		private void OnError(Type middlewareType, IMiddleware middleware, object context, Exception ex)
		{
			Log("Error occurred while running middleware:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tMiddleware Type = {3}{0}\tMiddleware = {4}", Environment.NewLine, ID, context, middleware.GetType(), middleware);
				
			if (Error == null) return;

			var args = new MiddlewareErrorEventArgs(Activator, middlewareType, middleware, context, ex);
			Task.Factory.StartNew(() => Error(this, args));
		}

		private void OnRequestReceived(object context)
		{
			Log("New request received:{0}\tPipeline ID = {1}{0}\tContext = {2}", Environment.NewLine, ID, context);
			
			if (RequestReceived == null) return;

			var args = new RequestReceivedEventArgs(Activator, context);
			Task.Factory.StartNew(() => RequestReceived(this, args));
		}

		private void OnRequestCompleted(object context, bool completedPipeline)
		{
			Log("Finished processing request:{0}\tPipeline ID = {1}{0}\tContext = {2}{0}\tUsed Entire Pipeline = {3}", Environment.NewLine, ID, context, completedPipeline);
			
			if (RequestCompleted == null) return;

			var args = new RequestCompletedEventArgs(Activator, context, completedPipeline);
			Task.Factory.StartNew(() => RequestCompleted(this, args));
		}
		
		private void OnMiddlewareInitialize(IMiddleware middleware)
		{
			if (MiddlewareInitialize == null) return;

			var args = new MiddlewareInitializeEventArgs(middleware);
			var task = Task.Factory.StartNew(() => MiddlewareInitialize(this, args));
			task.ContinueWith(tas1 =>
			{
				if (task.IsFaulted)
					task.Dispose();
			});
		}

		internal Action<string> Logger
		{
			set
			{
				_logger = value ?? EmptyTrace;
			}
		}

		private static void EmptyTrace(string logMessage)
		{
		}

		private void Log(string format, params object[] args)
		{
			Task.Run(() => _logger.Invoke(string.Format(CultureInfo.InvariantCulture, format, args)));
		}
	}
}
